/***************************************************************************
*   Group Members: Carl Huskey, Dawne Flanagan
*
*   MyServerSocket class
*   ====================
*
***************************************************************************/

import java.io.*;
import java.net.*;
import java.util.concurrent.*; // used for array blocking queue

public class MyServerSocket implements Runnable
{
    // declare variables
    int port = 0;
    int maxConn = 0;
    private boolean size;
    private boolean status;
    private boolean outputStatus;
    private DatagramSocket servSock = null;
    private ArrayBlockingQueue <DatagramPacket> connectionRequest;
    Thread incomingThread, outgoingThread;

    // create hash table
    private ConcurrentHashMap <String, ArrayBlockingQueue<DatagramPacket>> lookupTable;
    ArrayBlockingQueue<DatagramPacket> input, output;

    // overloaded constructor for the class that takes as a parameter the
    // port number and returns a new server socket on the specified port number
    public MyServerSocket(int clientPort, int maxConns) throws IOException
    {
        // setup server conditions
        this.port = clientPort;
        this.maxConn = maxConns;
        this.size = false;
        this.status = false;
        this.outputStatus = false;
        this.input = new ArrayBlockingQueue<DatagramPacket>(1024);
        this.output = new ArrayBlockingQueue<DatagramPacket>(1024);

        // setup connection request and the hash lookup table for maximum of user connections
        connectionRequest = new ArrayBlockingQueue<DatagramPacket>(maxConn);
        lookupTable = new ConcurrentHashMap<String, ArrayBlockingQueue<DatagramPacket>>(maxConn);

        // if the port number is already in use, an error message should be printed.
        try
        {
            servSock = new DatagramSocket(port);
        }
        catch (SocketException er)
        {
            System.err.println("Port is currently in use, Closing Connection");
            System.exit(0);
        }
    }

    // this method handles an incoming connection request and reserves the
    // necessary buffer space for the connection.
    public synchronized MyConnectionSocket accept() throws IOException, InterruptedException
    {
        byte[] send = null;
        DatagramPacket packet = null;
        MyConnectionSocket myConnSock = null;
        Packet msg = new Packet();
        ObjectOutputStream outputStream = null;
        ByteArrayOutputStream outbyteStream = new ByteArrayOutputStream(5000);


        // start incoming and outgoing server threads
        incomingThread.start();
        outgoingThread.start();

        // display ready on server side and port in which it is ready on
        System.out.println("\nServer ready...");

        try
        {
            if(lookupTable.isEmpty() || lookupTable.size() < maxConn)
            {
                msg.setJob(0);
            }
            else
            {
                msg.setJob(4);
            }

            status = true;
            packet = this.connectionRequest.take();

            // create new object queue
            ArrayBlockingQueue <DatagramPacket> queue = new ArrayBlockingQueue<DatagramPacket>(512);

            // create new output stream
            outputStream = new ObjectOutputStream(new BufferedOutputStream(outbyteStream));

            outputStream.writeObject(msg);
            // flushes this output stream and forces any buffered output bytes to be written out
            outputStream.flush();
            send = outbyteStream.toByteArray();

            DatagramPacket response = new DatagramPacket(send, send.length, packet.getAddress(), packet.getPort());
            myConnSock = new MyConnectionSocket(packet.getAddress(), packet.getPort(), queue, output);
            lookupTable.put((String)(response.getAddress() + ":" + response.getPort()), queue);

            if(msg.getJob().equals("accept"))
            {
                this.output.add(response);
            }
        }
        catch(Exception er)
        {
            System.out.println("Cannot Send");
        }
        // returns an object of ConnectionSocket class
        return myConnSock;
    }

    // returns a hash table supporting full concurrency
    // of retrievals and adjustable expected concurrent data.
    public ConcurrentHashMap <String, ArrayBlockingQueue<DatagramPacket>> getSocketHashTable()
    {
        return lookupTable;
    }
    // return the server socket number
    public DatagramSocket getDataSocket()
    {
        return servSock;
    }

    // return the number of connection request from blocking queue
    public ArrayBlockingQueue<DatagramPacket> getConnectionRequest()
    {
        return this.connectionRequest;
    }

    // return the output buffer from blocking queue
    public ArrayBlockingQueue<DatagramPacket> getOutputBuffer()
    {
        return output;
    }

    // return the maximum number of connections allowed
    public int getConnLimit()
    {
        return maxConn;
    }

    // return boolean value for accept flag
    public boolean getStatusFlag()
    {
        return status;
    }

    // set accept
    public void setStatusFlag(boolean flag)
    {
        this.status = flag;
    }

	@Override
	public void run() {
		// TODO Auto-generated method stub
		
	}
}
